// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/renderer_host/input/render_widget_host_latency_tracker.h"

#include <stddef.h>

#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "build/build_config.h"
#include "components/rappor/public/rappor_utils.h"
#include "content/browser/renderer_host/render_widget_host_delegate.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/common/content_client.h"
#include "ui/events/blink/web_input_event_traits.h"

using blink::WebGestureEvent;
using blink::WebInputEvent;
using blink::WebMouseEvent;
using blink::WebMouseWheelEvent;
using blink::WebTouchEvent;
using ui::LatencyInfo;

namespace content {
namespace {

    void UpdateLatencyCoordinatesImpl(const blink::WebTouchEvent& touch,
        LatencyInfo* latency,
        float device_scale_factor)
    {
        for (uint32_t i = 0; i < touch.touchesLength; ++i) {
            gfx::PointF coordinate(touch.touches[i].position.x * device_scale_factor,
                touch.touches[i].position.y * device_scale_factor);
            if (!latency->AddInputCoordinate(coordinate))
                break;
        }
    }

    void UpdateLatencyCoordinatesImpl(const WebGestureEvent& gesture,
        LatencyInfo* latency,
        float device_scale_factor)
    {
        latency->AddInputCoordinate(gfx::PointF(gesture.x * device_scale_factor,
            gesture.y * device_scale_factor));
    }

    void UpdateLatencyCoordinatesImpl(const WebMouseEvent& mouse,
        LatencyInfo* latency,
        float device_scale_factor)
    {
        latency->AddInputCoordinate(gfx::PointF(mouse.x * device_scale_factor,
            mouse.y * device_scale_factor));
    }

    void UpdateLatencyCoordinatesImpl(const WebMouseWheelEvent& wheel,
        LatencyInfo* latency,
        float device_scale_factor)
    {
        latency->AddInputCoordinate(gfx::PointF(wheel.x * device_scale_factor,
            wheel.y * device_scale_factor));
    }

    void UpdateLatencyCoordinates(const WebInputEvent& event,
        float device_scale_factor,
        LatencyInfo* latency)
    {
        if (WebInputEvent::isMouseEventType(event.type())) {
            UpdateLatencyCoordinatesImpl(static_cast<const WebMouseEvent&>(event),
                latency, device_scale_factor);
        } else if (WebInputEvent::isGestureEventType(event.type())) {
            UpdateLatencyCoordinatesImpl(static_cast<const WebGestureEvent&>(event),
                latency, device_scale_factor);
        } else if (WebInputEvent::isTouchEventType(event.type())) {
            UpdateLatencyCoordinatesImpl(static_cast<const WebTouchEvent&>(event),
                latency, device_scale_factor);
        } else if (event.type() == WebInputEvent::MouseWheel) {
            UpdateLatencyCoordinatesImpl(static_cast<const WebMouseWheelEvent&>(event),
                latency, device_scale_factor);
        }
    }

// Touch to scroll latency that is mostly under 1 second.
#define UMA_HISTOGRAM_TOUCH_TO_SCROLL_LATENCY(name, start, end)                 \
    UMA_HISTOGRAM_CUSTOM_COUNTS(                                                \
        name, (end.event_time - start.event_time).InMicroseconds(), 1, 1000000, \
        100)

// Long scroll latency component that is mostly under 200ms.
#define UMA_HISTOGRAM_SCROLL_LATENCY_LONG(name, start, end)   \
    UMA_HISTOGRAM_CUSTOM_COUNTS(                              \
        name,                                                 \
        (end.event_time - start.event_time).InMicroseconds(), \
        1000, 200000, 50)

// Short scroll latency component that is mostly under 50ms.
#define UMA_HISTOGRAM_SCROLL_LATENCY_SHORT(name, start, end)  \
    UMA_HISTOGRAM_CUSTOM_COUNTS(                              \
        name,                                                 \
        (end.event_time - start.event_time).InMicroseconds(), \
        1, 50000, 50)

// Check valid timing for start and end latency components.
#define CONFIRM_VALID_TIMING(start, end)       \
    DCHECK(!start.first_event_time.is_null()); \
    DCHECK(!end.last_event_time.is_null());    \
    DCHECK_GE(end.last_event_time, start.first_event_time);

// Touch/wheel to scroll latency using Rappor.
#define RAPPOR_TOUCH_WHEEL_TO_SCROLL_LATENCY(delegate, name, start, end)                                \
    CONFIRM_VALID_TIMING(start, end)                                                                    \
    rappor::RapporService* rappor_service = GetContentClient()->browser()->GetRapporService();          \
    if (rappor_service && delegate) {                                                                   \
        std::unique_ptr<rappor::Sample> sample = rappor_service->CreateSample(rappor::UMA_RAPPOR_TYPE); \
        delegate->AddDomainInfoToRapporSample(sample.get());                                            \
        sample->SetUInt64Field(                                                                         \
            "Latency",                                                                                  \
            (end.last_event_time - start.first_event_time).InMicroseconds(),                            \
            rappor::NO_NOISE);                                                                          \
        rappor_service->RecordSample(name, std::move(sample));                                          \
    }

// Touch/wheel to scroll latency that is mostly under 1 second.
#define UMA_HISTOGRAM_TOUCH_WHEEL_TO_SCROLL_LATENCY(name, start, end) \
    CONFIRM_VALID_TIMING(start, end)                                  \
    base::Histogram::FactoryGet(name, 1, 1000000, 100,                \
        base::HistogramBase::kUmaTargetedHistogramFlag)               \
        ->Add((end.last_event_time - start.first_event_time).InMicroseconds());

// Long touch/wheel scroll latency component that is mostly under 200ms.
#define UMA_HISTOGRAM_SCROLL_LATENCY_LONG_2(name, start, end) \
    CONFIRM_VALID_TIMING(start, end)                          \
    base::Histogram::FactoryGet(name, 1000, 200000, 50,       \
        base::HistogramBase::kUmaTargetedHistogramFlag)       \
        ->Add((end.last_event_time - start.first_event_time).InMicroseconds());

// Short touch/wheel scroll latency component that is mostly under 50ms.
#define UMA_HISTOGRAM_SCROLL_LATENCY_SHORT_2(name, start, end) \
    CONFIRM_VALID_TIMING(start, end)                           \
    base::Histogram::FactoryGet(name, 1, 50000, 50,            \
        base::HistogramBase::kUmaTargetedHistogramFlag)        \
        ->Add((end.last_event_time - start.first_event_time).InMicroseconds());

    void ComputeScrollLatencyHistograms(
        const LatencyInfo::LatencyComponent& gpu_swap_begin_component,
        const LatencyInfo::LatencyComponent& gpu_swap_end_component,
        int64_t latency_component_id,
        const LatencyInfo& latency,
        bool is_running_navigation_hint_task)
    {
        DCHECK(!latency.coalesced());
        if (latency.coalesced())
            return;

        DCHECK(!gpu_swap_begin_component.event_time.is_null());
        DCHECK(!gpu_swap_end_component.event_time.is_null());
        LatencyInfo::LatencyComponent original_component;
        if (latency.FindLatency(
                ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT,
                latency_component_id, &original_component)) {
            // This UMA metric tracks the time between the final frame swap for the
            // first scroll event in a sequence and the original timestamp of that
            // scroll event's underlying touch event.
            for (size_t i = 0; i < original_component.event_count; i++) {
                UMA_HISTOGRAM_TOUCH_TO_SCROLL_LATENCY(
                    "Event.Latency.TouchToFirstScrollUpdateSwapBegin",
                    original_component, gpu_swap_begin_component);
            }
            // TODO(horo): IsRunningNavigationHintTask UMAs are only for
            // SpeculativeLaunchServiceWorker experimentation. So remove this UMA when
            // the experimentation finished (crbug.com/638827).
            if (is_running_navigation_hint_task) {
                for (size_t i = 0; i < original_component.event_count; i++) {
                    UMA_HISTOGRAM_TOUCH_TO_SCROLL_LATENCY(
                        "Event.Latency.TouchToFirstScrollUpdateSwapBegin_"
                        "IsRunningNavigationHintTask",
                        original_component, gpu_swap_begin_component);
                }
            }
        } else if (!latency.FindLatency(
                       ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT,
                       latency_component_id, &original_component)) {
            return;
        }

        // This UMA metric tracks the time from when the original touch event is
        // created to when the scroll gesture results in final frame swap.
        for (size_t i = 0; i < original_component.event_count; i++) {
            UMA_HISTOGRAM_TOUCH_TO_SCROLL_LATENCY(
                "Event.Latency.TouchToScrollUpdateSwapBegin", original_component,
                gpu_swap_begin_component);
        }
        // TODO(horo): IsRunningNavigationHintTask UMAs are only for
        // SpeculativeLaunchServiceWorker experimentation. So remove this UMA when
        // the experimentation finished (crbug.com/638827).
        if (is_running_navigation_hint_task) {
            for (size_t i = 0; i < original_component.event_count; i++) {
                UMA_HISTOGRAM_TOUCH_TO_SCROLL_LATENCY(
                    "Event.Latency.TouchToScrollUpdateSwapBegin_"
                    "IsRunningNavigationHintTask",
                    original_component, gpu_swap_begin_component);
            }
        }

        // TODO(miletus): Add validation for making sure the following components
        // are present and their event times are legit.
        LatencyInfo::LatencyComponent rendering_scheduled_component;
        bool rendering_scheduled_on_main = latency.FindLatency(
            ui::INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_MAIN_COMPONENT,
            0, &rendering_scheduled_component);

        if (!rendering_scheduled_on_main) {
            if (!latency.FindLatency(
                    ui::INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_IMPL_COMPONENT,
                    0, &rendering_scheduled_component))
                return;
        }

        if (rendering_scheduled_on_main) {
            UMA_HISTOGRAM_SCROLL_LATENCY_LONG(
                "Event.Latency.ScrollUpdate.TouchToHandled_Main",
                original_component, rendering_scheduled_component);
        } else {
            UMA_HISTOGRAM_SCROLL_LATENCY_LONG(
                "Event.Latency.ScrollUpdate.TouchToHandled_Impl",
                original_component, rendering_scheduled_component);
        }

        LatencyInfo::LatencyComponent renderer_swap_component;
        if (!latency.FindLatency(ui::INPUT_EVENT_LATENCY_RENDERER_SWAP_COMPONENT,
                0, &renderer_swap_component))
            return;

        if (rendering_scheduled_on_main) {
            UMA_HISTOGRAM_SCROLL_LATENCY_LONG(
                "Event.Latency.ScrollUpdate.HandledToRendererSwap_Main",
                rendering_scheduled_component, renderer_swap_component);
        } else {
            UMA_HISTOGRAM_SCROLL_LATENCY_LONG(
                "Event.Latency.ScrollUpdate.HandledToRendererSwap_Impl",
                rendering_scheduled_component, renderer_swap_component);
        }

        LatencyInfo::LatencyComponent browser_received_swap_component;
        if (!latency.FindLatency(
                ui::INPUT_EVENT_BROWSER_RECEIVED_RENDERER_SWAP_COMPONENT,
                0, &browser_received_swap_component))
            return;

        UMA_HISTOGRAM_SCROLL_LATENCY_SHORT(
            "Event.Latency.ScrollUpdate.RendererSwapToBrowserNotified",
            renderer_swap_component, browser_received_swap_component);

        UMA_HISTOGRAM_SCROLL_LATENCY_LONG(
            "Event.Latency.ScrollUpdate.BrowserNotifiedToBeforeGpuSwap",
            browser_received_swap_component, gpu_swap_begin_component);

        UMA_HISTOGRAM_SCROLL_LATENCY_SHORT("Event.Latency.ScrollUpdate.GpuSwap",
            gpu_swap_begin_component,
            gpu_swap_end_component);
    }

    void ComputeTouchAndWheelScrollLatencyHistograms(
        RenderWidgetHostDelegate* render_widget_host_delegate,
        const ui::LatencyInfo::LatencyComponent& gpu_swap_begin_component,
        const ui::LatencyInfo::LatencyComponent& gpu_swap_end_component,
        int64_t latency_component_id,
        const ui::LatencyInfo& latency,
        const std::string event_type_name)
    {
        DCHECK(!latency.coalesced());
        DCHECK(event_type_name == "Touch" || event_type_name == "Wheel");
        if (latency.coalesced())
            return;

        LatencyInfo::LatencyComponent original_component;
        std::string scroll_name = "ScrollUpdate";
        if (latency.FindLatency(
                ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT,
                latency_component_id, &original_component)) {
            scroll_name = "ScrollBegin";
            // This UMA metric tracks the time between the final frame swap for the
            // first scroll event in a sequence and the original timestamp of that
            // scroll event's underlying touch/wheel event.

            UMA_HISTOGRAM_TOUCH_WHEEL_TO_SCROLL_LATENCY(
                "Event.Latency.ScrollBegin." + event_type_name + ".TimeToScrollUpdateSwapBegin2",
                original_component, gpu_swap_begin_component);

            RAPPOR_TOUCH_WHEEL_TO_SCROLL_LATENCY(
                render_widget_host_delegate,
                "Event.Latency.ScrollBegin." + event_type_name + ".TimeToScrollUpdateSwapBegin2",
                original_component, gpu_swap_begin_component);

            // TODO(lanwei): Will remove them when M56 is stable, see
            // https://crbug.com/669618.
            UMA_HISTOGRAM_TOUCH_WHEEL_TO_SCROLL_LATENCY(
                "Event.Latency.ScrollUpdate." + event_type_name + ".TimeToFirstScrollUpdateSwapBegin2",
                original_component, gpu_swap_begin_component);
        } else if (latency.FindLatency(
                       ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT,
                       latency_component_id, &original_component)) {
            // This UMA metric tracks the time from when the original touch event is
            // created to when the scroll gesture results in final frame swap.
            // First scroll events are excluded from this metric.
            if (event_type_name == "Touch") {
                UMA_HISTOGRAM_TOUCH_WHEEL_TO_SCROLL_LATENCY(
                    "Event.Latency.ScrollUpdate." + event_type_name + ".TimeToScrollUpdateSwapBegin2",
                    original_component, gpu_swap_begin_component);

                RAPPOR_TOUCH_WHEEL_TO_SCROLL_LATENCY(
                    render_widget_host_delegate,
                    "Event.Latency.ScrollUpdate.Touch.TimeToScrollUpdateSwapBegin2",
                    original_component, gpu_swap_begin_component);
            }
        } else {
            // No original component found.
            return;
        }

        LatencyInfo::LatencyComponent rendering_scheduled_component;
        bool rendering_scheduled_on_main = latency.FindLatency(
            ui::INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_MAIN_COMPONENT, 0,
            &rendering_scheduled_component);
        if (!rendering_scheduled_on_main) {
            if (!latency.FindLatency(
                    ui::INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_IMPL_COMPONENT, 0,
                    &rendering_scheduled_component))
                return;
        }

        const std::string thread_name = rendering_scheduled_on_main ? "Main" : "Impl";

        UMA_HISTOGRAM_SCROLL_LATENCY_LONG_2(
            "Event.Latency." + scroll_name + "." + event_type_name + ".TimeToHandled2_" + thread_name,
            original_component, rendering_scheduled_component);

        LatencyInfo::LatencyComponent renderer_swap_component;
        if (!latency.FindLatency(ui::INPUT_EVENT_LATENCY_RENDERER_SWAP_COMPONENT, 0,
                &renderer_swap_component))
            return;

        UMA_HISTOGRAM_SCROLL_LATENCY_LONG_2(
            "Event.Latency." + scroll_name + "." + event_type_name + ".HandledToRendererSwap2_" + thread_name,
            rendering_scheduled_component, renderer_swap_component);

        LatencyInfo::LatencyComponent browser_received_swap_component;
        if (!latency.FindLatency(
                ui::INPUT_EVENT_BROWSER_RECEIVED_RENDERER_SWAP_COMPONENT, 0,
                &browser_received_swap_component))
            return;

        UMA_HISTOGRAM_SCROLL_LATENCY_SHORT_2(
            "Event.Latency." + scroll_name + "." + event_type_name + ".RendererSwapToBrowserNotified2",
            renderer_swap_component, browser_received_swap_component);

        UMA_HISTOGRAM_SCROLL_LATENCY_LONG_2(
            "Event.Latency." + scroll_name + "." + event_type_name + ".BrowserNotifiedToBeforeGpuSwap2",
            browser_received_swap_component, gpu_swap_begin_component);

        UMA_HISTOGRAM_SCROLL_LATENCY_SHORT_2(
            "Event.Latency." + scroll_name + "." + event_type_name + ".GpuSwap2",
            gpu_swap_begin_component, gpu_swap_end_component);
    }
    // LatencyComponents generated in the renderer must have component IDs
    // provided to them by the browser process. This function adds the correct
    // component ID where necessary.
    void AddLatencyInfoComponentIds(LatencyInfo* latency,
        int64_t latency_component_id)
    {
        std::vector<std::pair<ui::LatencyComponentType, int64_t>> new_components_key;
        std::vector<LatencyInfo::LatencyComponent> new_components_value;
        for (const auto& lc : latency->latency_components()) {
            ui::LatencyComponentType component_type = lc.first.first;
            if (component_type == ui::WINDOW_SNAPSHOT_FRAME_NUMBER_COMPONENT) {
                // Generate a new component entry with the correct component ID
                new_components_key.push_back(std::make_pair(component_type,
                    latency_component_id));
                new_components_value.push_back(lc.second);
            }
        }

        // Remove the entries with invalid component IDs.
        latency->RemoveLatency(ui::WINDOW_SNAPSHOT_FRAME_NUMBER_COMPONENT);

        // Add newly generated components into the latency info
        for (size_t i = 0; i < new_components_key.size(); i++) {
            latency->AddLatencyNumberWithTimestamp(
                new_components_key[i].first,
                new_components_key[i].second,
                new_components_value[i].sequence_number,
                new_components_value[i].event_time,
                new_components_value[i].event_count);
        }
    }

} // namespace

RenderWidgetHostLatencyTracker::RenderWidgetHostLatencyTracker()
    : last_event_id_(0)
    , latency_component_id_(0)
    , device_scale_factor_(1)
    , has_seen_first_gesture_scroll_update_(false)
    , multi_finger_gesture_(false)
    , touch_start_default_prevented_(false)
    , render_widget_host_delegate_(nullptr)
{
}

RenderWidgetHostLatencyTracker::~RenderWidgetHostLatencyTracker() { }

void RenderWidgetHostLatencyTracker::Initialize(int routing_id,
    int process_id)
{
    DCHECK_EQ(0, last_event_id_);
    DCHECK_EQ(0, latency_component_id_);
    last_event_id_ = static_cast<int64_t>(process_id) << 32;
    latency_component_id_ = routing_id | last_event_id_;
}

void RenderWidgetHostLatencyTracker::ComputeInputLatencyHistograms(
    WebInputEvent::Type type,
    int64_t latency_component_id,
    const LatencyInfo& latency,
    InputEventAckState ack_result)
{
    if (latency.coalesced())
        return;

    LatencyInfo::LatencyComponent rwh_component;
    if (!latency.FindLatency(ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
            latency_component_id, &rwh_component)) {
        return;
    }
    DCHECK_EQ(rwh_component.event_count, 1u);

    LatencyInfo::LatencyComponent ui_component;
    if (latency.FindLatency(ui::INPUT_EVENT_LATENCY_UI_COMPONENT, 0,
            &ui_component)) {
        DCHECK_EQ(ui_component.event_count, 1u);
        base::TimeDelta ui_delta = rwh_component.event_time - ui_component.event_time;

        if (type == blink::WebInputEvent::MouseWheel) {
            UMA_HISTOGRAM_CUSTOM_COUNTS("Event.Latency.Browser.WheelUI",
                ui_delta.InMicroseconds(), 1, 20000, 100);

        } else {
            DCHECK(WebInputEvent::isTouchEventType(type));
            UMA_HISTOGRAM_CUSTOM_COUNTS("Event.Latency.Browser.TouchUI",
                ui_delta.InMicroseconds(), 1, 20000, 100);
        }
    }

    // Both tap and scroll gestures depend on the disposition of the touch start
    // and the current touch. For touch start, touch_start_default_prevented_ ==
    // (ack_result == INPUT_EVENT_ACK_STATE_CONSUMED).
    bool action_prevented = touch_start_default_prevented_ || ack_result == INPUT_EVENT_ACK_STATE_CONSUMED;

    LatencyInfo::LatencyComponent main_component;
    if (latency.FindLatency(ui::INPUT_EVENT_LATENCY_RENDERER_MAIN_COMPONENT, 0,
            &main_component)) {
        DCHECK_EQ(main_component.event_count, 1u);
        base::TimeDelta queueing_delta = main_component.event_time - rwh_component.event_time;

        if (!multi_finger_gesture_) {
            if (action_prevented) {
                switch (type) {
                case WebInputEvent::TouchStart:
                    UMA_HISTOGRAM_TIMES(
                        "Event.Latency.QueueingTime.TouchStartDefaultPrevented",
                        queueing_delta);
                    break;
                case WebInputEvent::TouchMove:
                    UMA_HISTOGRAM_TIMES(
                        "Event.Latency.QueueingTime.TouchMoveDefaultPrevented",
                        queueing_delta);
                    break;
                case WebInputEvent::TouchEnd:
                    UMA_HISTOGRAM_TIMES(
                        "Event.Latency.QueueingTime.TouchEndDefaultPrevented",
                        queueing_delta);
                    break;
                default:
                    break;
                }
            } else {
                switch (type) {
                case WebInputEvent::TouchStart:
                    UMA_HISTOGRAM_TIMES(
                        "Event.Latency.QueueingTime.TouchStartDefaultAllowed",
                        queueing_delta);
                    break;
                case WebInputEvent::TouchMove:
                    UMA_HISTOGRAM_TIMES(
                        "Event.Latency.QueueingTime.TouchMoveDefaultAllowed",
                        queueing_delta);
                    break;
                case WebInputEvent::TouchEnd:
                    UMA_HISTOGRAM_TIMES(
                        "Event.Latency.QueueingTime.TouchEndDefaultAllowed",
                        queueing_delta);
                    break;
                default:
                    break;
                }
            }
        }
    }

    LatencyInfo::LatencyComponent acked_component;
    if (latency.FindLatency(ui::INPUT_EVENT_LATENCY_ACK_RWH_COMPONENT, 0,
            &acked_component)) {
        DCHECK_EQ(acked_component.event_count, 1u);
        base::TimeDelta acked_delta = acked_component.event_time - rwh_component.event_time;
        if (type == blink::WebInputEvent::MouseWheel) {
            UMA_HISTOGRAM_CUSTOM_COUNTS("Event.Latency.Browser.WheelAcked",
                acked_delta.InMicroseconds(), 1, 1000000,
                100);
        } else {
            DCHECK(WebInputEvent::isTouchEventType(type));
            UMA_HISTOGRAM_CUSTOM_COUNTS("Event.Latency.Browser.TouchAcked",
                acked_delta.InMicroseconds(), 1, 1000000,
                100);
        }

        if (!multi_finger_gesture_ && main_component.event_time != base::TimeTicks()) {
            base::TimeDelta blocking_delta;
            blocking_delta = acked_component.event_time - main_component.event_time;

            if (action_prevented) {
                switch (type) {
                case WebInputEvent::TouchStart:
                    UMA_HISTOGRAM_TIMES(
                        "Event.Latency.BlockingTime.TouchStartDefaultPrevented",
                        blocking_delta);
                    break;
                case WebInputEvent::TouchMove:
                    UMA_HISTOGRAM_TIMES(
                        "Event.Latency.BlockingTime.TouchMoveDefaultPrevented",
                        blocking_delta);
                    break;
                case WebInputEvent::TouchEnd:
                    UMA_HISTOGRAM_TIMES(
                        "Event.Latency.BlockingTime.TouchEndDefaultPrevented",
                        blocking_delta);
                    break;
                default:
                    break;
                }
            } else {
                switch (type) {
                case WebInputEvent::TouchStart:
                    UMA_HISTOGRAM_TIMES(
                        "Event.Latency.BlockingTime.TouchStartDefaultAllowed",
                        blocking_delta);
                    break;
                case WebInputEvent::TouchMove:
                    UMA_HISTOGRAM_TIMES(
                        "Event.Latency.BlockingTime.TouchMoveDefaultAllowed",
                        blocking_delta);
                    break;
                case WebInputEvent::TouchEnd:
                    UMA_HISTOGRAM_TIMES(
                        "Event.Latency.BlockingTime.TouchEndDefaultAllowed",
                        blocking_delta);
                    break;
                default:
                    break;
                }
            }
        }
    }
}

void RenderWidgetHostLatencyTracker::OnInputEvent(
    const blink::WebInputEvent& event,
    LatencyInfo* latency)
{
    DCHECK(latency);
    if (latency->FindLatency(ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
            latency_component_id_, NULL)) {
        return;
    }

    if (event.timeStampSeconds() && !latency->FindLatency(ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0, NULL)) {
        base::TimeTicks timestamp_now = base::TimeTicks::Now();
        base::TimeTicks timestamp_original = base::TimeTicks() + base::TimeDelta::FromSecondsD(event.timeStampSeconds());

        // Timestamp from platform input can wrap, e.g. 32 bits timestamp
        // for Xserver and Window MSG time will wrap about 49.6 days. Do a
        // sanity check here and if wrap does happen, use TimeTicks::Now()
        // as the timestamp instead.
        if ((timestamp_now - timestamp_original).InDays() > 0)
            timestamp_original = timestamp_now;

        latency->AddLatencyNumberWithTimestamp(
            ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT,
            0,
            0,
            timestamp_original,
            1);
    }

    latency->AddLatencyNumberWithTraceName(
        ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, latency_component_id_,
        ++last_event_id_, WebInputEvent::GetName(event.type()));

    UpdateLatencyCoordinates(event, device_scale_factor_, latency);

    if (event.type() == blink::WebInputEvent::GestureScrollBegin) {
        has_seen_first_gesture_scroll_update_ = false;
    } else if (event.type() == blink::WebInputEvent::GestureScrollUpdate) {
        // Make a copy of the INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT with a
        // different name INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT.
        // So we can track the latency specifically for scroll update events.
        LatencyInfo::LatencyComponent original_component;
        if (latency->FindLatency(ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0,
                &original_component)) {
            latency->AddLatencyNumberWithTimestamp(
                has_seen_first_gesture_scroll_update_
                    ? ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT
                    : ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT,
                latency_component_id_, original_component.sequence_number,
                original_component.event_time, original_component.event_count);
        }

        has_seen_first_gesture_scroll_update_ = true;
    }
}

void RenderWidgetHostLatencyTracker::OnInputEventAck(
    const blink::WebInputEvent& event,
    LatencyInfo* latency, InputEventAckState ack_result)
{
    DCHECK(latency);

    // Latency ends when it is acked but does not cause render scheduling.
    bool rendering_scheduled = latency->FindLatency(
        ui::INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_MAIN_COMPONENT, 0, nullptr);
    rendering_scheduled |= latency->FindLatency(
        ui::INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_IMPL_COMPONENT, 0, nullptr);

    if (WebInputEvent::isGestureEventType(event.type())) {
        if (!rendering_scheduled) {
            latency->AddLatencyNumber(
                ui::INPUT_EVENT_LATENCY_TERMINATED_GESTURE_COMPONENT, 0, 0);
            // TODO(jdduke): Consider exposing histograms for gesture event types.
        }
        return;
    }

    if (WebInputEvent::isTouchEventType(event.type())) {
        const WebTouchEvent& touch_event = *static_cast<const WebTouchEvent*>(&event);
        if (event.type() == WebInputEvent::TouchStart) {
            DCHECK(touch_event.touchesLength >= 1);
            multi_finger_gesture_ = touch_event.touchesLength != 1;
            touch_start_default_prevented_ = ack_result == INPUT_EVENT_ACK_STATE_CONSUMED;
        }

        latency->AddLatencyNumber(ui::INPUT_EVENT_LATENCY_ACK_RWH_COMPONENT, 0, 0);

        if (!rendering_scheduled) {
            latency->AddLatencyNumber(
                ui::INPUT_EVENT_LATENCY_TERMINATED_TOUCH_COMPONENT, 0, 0);
        }
        ComputeInputLatencyHistograms(event.type(), latency_component_id_, *latency,
            ack_result);
        return;
    }

    if (event.type() == WebInputEvent::MouseWheel) {
        latency->AddLatencyNumber(ui::INPUT_EVENT_LATENCY_ACK_RWH_COMPONENT, 0, 0);
        if (!rendering_scheduled) {
            latency->AddLatencyNumber(
                ui::INPUT_EVENT_LATENCY_TERMINATED_MOUSE_WHEEL_COMPONENT, 0, 0);
        }
        ComputeInputLatencyHistograms(event.type(), latency_component_id_, *latency,
            ack_result);
        return;
    }

    if (WebInputEvent::isMouseEventType(event.type()) && !rendering_scheduled) {
        latency->AddLatencyNumber(
            ui::INPUT_EVENT_LATENCY_TERMINATED_MOUSE_COMPONENT, 0, 0);
        return;
    }

    if (WebInputEvent::isKeyboardEventType(event.type()) && !rendering_scheduled) {
        latency->AddLatencyNumber(
            ui::INPUT_EVENT_LATENCY_TERMINATED_KEYBOARD_COMPONENT, 0, 0);
        return;
    }
}

void RenderWidgetHostLatencyTracker::OnSwapCompositorFrame(
    std::vector<LatencyInfo>* latencies)
{
    DCHECK(latencies);
    for (LatencyInfo& latency : *latencies) {
        AddLatencyInfoComponentIds(&latency, latency_component_id_);
        latency.AddLatencyNumber(
            ui::INPUT_EVENT_BROWSER_RECEIVED_RENDERER_SWAP_COMPONENT, 0, 0);
    }
}

void RenderWidgetHostLatencyTracker::OnFrameSwapped(
    const LatencyInfo& latency,
    bool is_running_navigation_hint_task)
{

    LatencyInfo::LatencyComponent gpu_swap_end_component;
    if (!latency.FindLatency(
            ui::INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT, 0,
            &gpu_swap_end_component)) {
        return;
    }

    LatencyInfo::LatencyComponent gpu_swap_begin_component;
    if (!latency.FindLatency(ui::INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT, 0,
            &gpu_swap_begin_component)) {
        return;
    }

    LatencyInfo::LatencyComponent tab_switch_component;
    if (latency.FindLatency(ui::TAB_SHOW_COMPONENT, latency_component_id_,
            &tab_switch_component)) {
        base::TimeDelta delta = gpu_swap_end_component.event_time - tab_switch_component.event_time;
        for (size_t i = 0; i < tab_switch_component.event_count; i++) {
            UMA_HISTOGRAM_TIMES("MPArch.RWH_TabSwitchPaintDuration", delta);
        }
    }

    if (!latency.FindLatency(ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT,
            latency_component_id_, nullptr)) {
        return;
    }

    ui::SourceEventType source_event_type = latency.source_event_type();
    if (source_event_type == ui::SourceEventType::WHEEL || source_event_type == ui::SourceEventType::TOUCH) {
        ComputeTouchAndWheelScrollLatencyHistograms(
            render_widget_host_delegate_, gpu_swap_begin_component,
            gpu_swap_end_component, latency_component_id_, latency,
            source_event_type == ui::SourceEventType::WHEEL ? "Wheel" : "Touch");
    }

    // Compute the old scroll update latency metrics. They are exclusively
    // calculated for touch scrolls, and will be deprecated on M56.
    // (https://crbug.com/649754)
    LatencyInfo::LatencyComponent mouse_wheel_scroll_update_component;
    if (!latency.FindLatency(
            ui::INPUT_EVENT_LATENCY_GENERATE_SCROLL_UPDATE_FROM_MOUSE_WHEEL, 0,
            &mouse_wheel_scroll_update_component)) {
        ComputeScrollLatencyHistograms(
            gpu_swap_begin_component, gpu_swap_end_component, latency_component_id_,
            latency, is_running_navigation_hint_task);
    }
}

void RenderWidgetHostLatencyTracker::SetDelegate(
    RenderWidgetHostDelegate* delegate)
{
    render_widget_host_delegate_ = delegate;
}

} // namespace content
